
#include <BFont>
#include <BStyle>
#include <BSystem>
#include <BPainter>
#include <BMouseEvent>
#include <BScrollBar>

#include "DialogListWidget.h"

DialogListWidget::DialogListWidget(BWidget* parent) : BScrollArea(parent)
{
	_spacing = 3;
	_selectMode = Select_Multiple | Select_Continuous;
	_showItemBegin = -1;
	_showItemEnd = -1;
	_pressedItem = 0;
	_hoveredItem = 0;
	_dirty = false;
}
DialogListWidget::~DialogListWidget()
{

}

bool DialogListWidget::addItem(DialogListItem* item)
{
	if (item && !_items.contain(item))
	{
		_items.append(item);
		if (item->listWidget() != this)
		{
			if (item->listWidget())
				item->listWidget()->removeItem(item);
			item->_listWidget = this;
		}
		_dirty = true;
		this->fresh();
		return true;
	}
	return false;
}
DialogListItem* DialogListWidget::addItem(const BString& text)
{
	DialogListItem* item = new DialogListItem();
	item->setText(text);
	this->addItem(item);
	return item;
}
DialogListItem* DialogListWidget::addItem(BIcon* icon, const BString& text)
{
	DialogListItem* item = new DialogListItem();
	item->setIcon(icon);
	item->setText(text);
	this->addItem(item);
	return item;
}
DialogListItem* DialogListWidget::addItem(BImage* image, const BString& text)
{
	DialogListItem* item = new DialogListItem();
	item->setImage(image);
	item->setText(text);
	this->addItem(item);
	return item;
}

bool DialogListWidget::insertItem(int pos, DialogListItem* item)
{
	if (item && !_items.contain(item) && _items.check(pos))
	{
		_items.insert(pos, item);
		if (item->listWidget() != this)
		{
			if (item->listWidget())
				item->listWidget()->removeItem(item);
			item->_listWidget = this;
		}
		_dirty = true;
		this->fresh();
		return true;
	}
	return false;
}
DialogListItem* DialogListWidget::insertItem(int pos, const BString& text)
{
	BHolder<DialogListItem> item = new DialogListItem();
	item->setText(text);
	if (insertItem(pos, item))
		return item;
	return 0;
}
DialogListItem* DialogListWidget::insertItem(int pos, BIcon* icon, const BString& text)
{
	BHolder<DialogListItem> item = new DialogListItem();
	item->setIcon(icon);
	item->setText(text);
	if (insertItem(pos, item))
		return item;
	return 0;
}

bool DialogListWidget::removeItem(DialogListItem* item)
{
	if (_items.contain(item))
	{
		if (_hoveredItem == item)
			_hoveredItem = 0;
		if (_pressedItem == item)
			_pressedItem = 0;
		_selectedItems.remove(item);
		item->_listWidget = 0;
		_items.remove(item);
		_dirty = true;
		this->fresh();
		return true;
	}
	return false;
}
bool DialogListWidget::removeItem(const BString& text)
{
	DialogListItem* item = this->item(text);
	return removeItem(item);
}
bool DialogListWidget::removeItem(int index)
{
	DialogListItem* item = _items(index);
	return removeItem(item);
}

void DialogListWidget::clearItems()
{
	clearSelection();
	for (int i = 0; i < _items.size(); i++)
	{
		DialogListItem* item = _items[i];
		item->_listWidget = 0;
	}
	_items.clear();
	_hoveredItem = 0;
	_showItemBegin = -1;
	_showItemEnd = -1;
	this->fresh();
}
bool DialogListWidget::clearSelection()
{
	if (_selectedItems.size())
	{
		while (DialogListItem* item = _selectedItems.last())
		{
			item->_selected = false;
			_selectedItems -= item;
			emit(Signal_ItemSelected, item);
		}
		this->fresh();
	}
	return true;
}

void DialogListWidget::setSelectMode(SelectMode selectMode)
{
	if (_selectMode != selectMode)
	{
		_selectMode = selectMode;
		this->clearSelection();
		this->fresh();
	}
}
SelectMode DialogListWidget::selectMode() const
{
	return _selectMode;
}

void DialogListWidget::setIconSize(const BSize& iconSize)
{
	if (_iconSize != iconSize)
	{
		_iconSize = iconSize;
		for (int i = 0; i < _items.size(); i++)
		{
			DialogListItem* item = _items[i];
			item->_dirty = true;
		}
		_dirty = true;
		this->fresh();
	}
}
const BSize& DialogListWidget::iconSize() const
{
	return _iconSize;
}

bool DialogListWidget::setItemSelected(DialogListItem* item, bool selected)
{
	if (!item)
		return false;
	if (selected)
	{
		if (!_selectedItems.contain(item))
		{
			if (_selectMode & Select_Single)
				clearSelection();
			_selectedItems.append(item);
			item->_selected = true;
			emit(Signal_ItemSelected, item);
			this->fresh();
		}
	}
	else
	{
		if (_selectedItems.remove(item))
		{
			item->_selected = false;
			emit(Signal_ItemSelected, item);
			this->fresh();
		}
	}
	return true;
}

int DialogListWidget::selectedItemCount() const
{
	return _selectedItems.size();
}
DialogListItem* DialogListWidget::selectedItem(int index)
{
	return _selectedItems(index);
}
const DialogListItem* DialogListWidget::selectedItem(int index) const
{
	return _selectedItems(index);
}

int DialogListWidget::itemCount() const
{
	return _items.size();
}
DialogListItem* DialogListWidget::item(int index)
{
	return _items(index);
}
const DialogListItem* DialogListWidget::item(int index) const
{
	return _items(index);
}

DialogListItem* DialogListWidget::item(const BString& text)
{
	for (int i = 0; i < _items.size(); i++)
	{
		DialogListItem* item = _items[i];
		if (item->text() == text)
			return item;
	}
	return 0;
}
const DialogListItem* DialogListWidget::item(const BString& text) const
{
	for (int i = 0; i < _items.size(); i++)
	{
		DialogListItem* item = _items[i];
		if (item->text() == text)
			return item;
	}
	return 0;
}

DialogListItem* DialogListWidget::itemAt(const BPoint& point)
{
	if (_items.empty())
		return 0;
	if (_showItemBegin < 0)
		return 0;
	BPoint offset = this->area().pos();
	for (int i = _showItemBegin; i <= _showItemEnd; i++)
	{
		DialogListItem* item = _items[i];
		BRect abRect = item->_rect + offset;
		if (abRect.contain(point))
			return item;
	}
	return 0;
}
const DialogListItem* DialogListWidget::itemAt(const BPoint& point) const
{
	if (_items.empty())
		return 0;
	if (_showItemBegin < 0)
		return 0;
	BPoint offset = this->area().pos();
	for (int i = _showItemBegin; i <= _showItemEnd; i++)
	{
		DialogListItem* item = _items[i];
		BRect abRect = item->_rect + offset;
		if (abRect.contain(point))
			return item;
	}
	return 0;
}

DialogListItem* DialogListWidget::itemOfAsset(const BValue& asset)
{
	if (asset.empty())
		return 0;
	for (int i = 0; i < _items.size(); i++)
	{
		DialogListItem* item = _items[i];
		if (item->asset() == asset)
		{
			return item;
		}
	}
	return 0;
}
const DialogListItem* DialogListWidget::itemOfAsset(const BValue& asset) const
{
	if (asset.empty())
		return 0;
	for (int i = 0; i < _items.size(); i++)
	{
		const DialogListItem* item = _items[i];
		if (item->asset() == asset)
		{
			return item;
		}
	}
	return 0;
}

void DialogListWidget::scrollTo(DialogListItem* item, Align align)
{
	if (_dirty)
	{
		freshArea();
	}

	BRect crect = this->centerRect();
	BRect area = this->area();
	if (item)
	{
		BRect rect = item->rect() + area.pos();
		BRect rect1 = crect.align(align, rect.size());
		BPoint offset = rect1.pos() - rect.pos();
		area.move(offset);
	}
	else
	{
		area = crect.align(align, area.size());
	}
	this->setArea(area);
}
bool DialogListWidget::sort(int dir)
{
	if (dir == 0)
		return false;
	while (1)
	{
		_selectedItems.sort();
		bool bubble = false;
		for (int i = 1; i < _items.size(); i++)
		{
			DialogListItem* item0 = _items[i - 1];
			DialogListItem* item1 = _items[i];
			if (dir > 0 && item0->text() > item1->text())
			{
				_items.swap(i, i - 1);
				bubble = true;
			}
			if (dir < 0 && item0->text() < item1->text())
			{
				_items.swap(i, i - 1);
				bubble = true;
			}
		}
		if (!bubble)
			break;
	}
	return true;
}

void DialogListWidget::dirtyItem(DialogListItem* item)
{
	if (!_dirty)
	{
		_dirty = true;
		this->fresh();
	}
}
void DialogListWidget::freshArea()
{
	if (!_dirty)
		return;

	const BFont* font = this->realFont();

	this->verticalScrollBar()->setStep(font->size());

	int width = 0;
	int height = 0;
	for (int i = 0; i < _items.size(); i++)
	{
		DialogListItem* item = _items[i];
		if (item->_dirty)
		{
			item->freshSize(font);
			item->_dirty = false;
		}
		BSize& hintSize = item->_hintSize;
		width = bMax(hintSize.width(), width);
		height += _spacing + hintSize.height();
	}
	_minAreaSize.set(width, height);
	BRect area = this->area();
	area.setSize(width, height);
	this->setArea(area);

	{
		BRect area = this->area();
		int width = this->centerSize().width();
		int x = 0;
		int y = 0;
		for (int i = 0; i < _items.size(); i++)
		{
			DialogListItem* item = _items[i];
			int height = item->_hintSize.height();
			item->adjustRect(x, y, width, height);
			y += _spacing + height;
		}
	}
	_dirty = false;
}
void DialogListWidget::freshVisualItems()
{
	BRect area = this->area();
	BRect crect = this->centerRect();
	int top = crect.top();
	int bottom = crect.bottom();
	int width = crect.width();

	_showItemBegin = -1;
	_showItemEnd = -1;

	BPoint offset = area.pos();
	int x = area.x();
	int y = area.y();
	for (int i = 0; i < _items.size(); i++)
	{
		DialogListItem* item = _items[i];
		BRect rect = item->_rect + offset;
		if (rect.bottom() >= top)
		{
			_showItemBegin = i;
			_showItemEnd = i;
			while (++i < _items.size())
			{
				_showItemEnd = i;
				DialogListItem* item = _items[i];
				BRect rect = item->_rect + offset;
				if (rect.bottom() >= bottom)
					break;
			}
			break;
		}
	}
}
void DialogListWidget::scrolling(Orientation orientation)
{
	_dirty = true;
	this->fresh();
}

void DialogListWidget::resizeEvent(const BEvent& event)
{
	BScrollArea::resizeEvent(event);
	_dirty = true;
}

void DialogListWidget::styleEvent(const BEvent& event)
{
	BScrollArea::styleEvent(event);
	for (int i = 0; i < _items.size(); i++)
	{
		DialogListItem* item = _items[i];
		item->_dirty = true;
	}
	const BStyle* style = this->realStyle();
	const BFont* font = style->font();
	if (font)
	{
		this->verticalScrollBar()->setStep(font->size());
	}
	_dirty = true;
	this->fresh();
}
void DialogListWidget::freshEvent(const BEvent& event)
{
	BScrollArea::freshEvent(event);
	if (_dirty)
	{
		freshArea();
		freshVisualItems();
		_dirty = false;
	}
}
void DialogListWidget::paintEvent(const BEvent& event)
{
	BScrollArea::paintEvent(event);
	BPainter painter(this);

	if (_showItemBegin >= 0)
	{
		BPoint offset = this->area().pos();
		int begin = _showItemBegin;
		int end = _showItemEnd;
		for (int i = begin; i <= end; i++)
		{
			const DialogListItem* item = _items[i];
			BRect abRect = item->_rect + offset;
			if (item->_selected)
			{
				painter.setColor(Color_Background, State_Checked);
				painter.fillRect(abRect);
			}
			else if (_hoveredItem == item)
			{
				painter.setColor(Color_Background, State_Hovered);
				painter.fillRect(abRect);
			}
			BImage* image = item->_image;
			if (BIcon* icon = item->_icon)
			{
				if (item->_selected)
					image = icon->image(State_Selected);
				else if (_hoveredItem == item)
					image = icon->image(State_Hovered);
				else
					image = icon->image(State_Normal);
			}
			if (image)
			{
				BRect imageRect = item->_imageRect + offset;
				painter.drawImage(imageRect, image);
			}
			if (const BFont* font = painter.realFont())
			{
				BRect textRect = item->_textRect + offset;
				painter.setColor(Color_Text);
				painter.drawText(textRect, item->text());
			}
		}
	}
}

void DialogListWidget::mouseEnterEvent(const BMouseEvent& mouseEvent)
{
	_hoveredItem = 0;
	BScrollArea::mouseEnterEvent(mouseEvent);
}
void DialogListWidget::mouseLeaveEvent(const BMouseEvent& mouseEvent)
{
	if (_hoveredItem != 0)
	{
		_hoveredItem = 0;
		fresh();
	}
	BScrollArea::mouseLeaveEvent(mouseEvent);
}

void DialogListWidget::mousePressEvent(const BMouseEvent& mouseEvent)
{
	const BPoint& pos = mouseEvent.pos();
	if (_pressedItem != _hoveredItem)
	{
		_pressedItem = _hoveredItem;
		this->fresh();
	}
	if (_selectMode & Select_Multiple)
	{
		DialogListItem* item = _hoveredItem;
		if (mouseEvent.keyState(Key_LCtrl))
		{
			setItemSelected(item, !item->_selected);
		}
		else if (mouseEvent.keyState(Key_LShift))
		{
			if (_selectedItems.empty())
			{
				setItemSelected(item, true);
			}
			else
			{
				if (item)
				{
					DialogListItem* firstItem = _selectedItems.first();
					int index0 = _items.index(firstItem);
					int index1 = _items.index(item);
					int min = bMin(index0, index1);
					int max = bMax(index0, index1);
					for (int i = 0; i < _selectedItems.size(); i++)
					{
						DialogListItem* sitem = _selectedItems[i];
						int index = _items.index(sitem);
						if (index < min || index > max)
						{
							_selectedItems.remove(i--);
							sitem->_selected = false;
							emit(Signal_ItemSelected, sitem);
							this->fresh();
						}
					}
					for (int i = min; i <= max; i++)
					{
						DialogListItem* sitem = _items[i];
						_selectedItems.append(sitem);
						sitem->_selected = true;
						emit(Signal_ItemSelected, sitem);
						this->fresh();
					}
				}
			}
		}
		else
		{
			if (item)
			{
				for (int i = 0; i < _selectedItems.size(); i++)
				{
					DialogListItem* sitem = _selectedItems[i];
					if (sitem != item)
					{
						_selectedItems.remove(i--);
						sitem->_selected = false;
						emit(Signal_ItemSelected, sitem);
						this->fresh();
					}
				}
				if (_selectedItems.empty())
				{
					_selectedItems.append(item);
					item->_selected = true;
					emit(Signal_ItemSelected, item);
					this->fresh();
				}
			}
			else
			{
				this->clearSelection();
			}
		}
		return;
	}
	if (_selectMode & Select_Single)
	{
		DialogListItem* item = _hoveredItem;
		if (item)
		{
			for (int i = 0; i < _selectedItems.size(); i++)
			{
				DialogListItem* sitem = _selectedItems[i];
				if (sitem != item)
				{
					_selectedItems.remove(i--);
					sitem->_selected = false;
					emit(Signal_ItemSelected, sitem);
					this->fresh();
				}
			}
			if (_selectedItems.empty())
			{
				_selectedItems.append(item);
				item->_selected = true;
				emit(Signal_ItemSelected, item);
				this->fresh();
			}
		}
		else
		{
			clearSelection();
		}
	}
	BScrollArea::mousePressEvent(mouseEvent);
}
void DialogListWidget::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	if (_pressedItem == 0)
	{
		_pressedItem = 0;
		this->fresh();
	}
	BScrollArea::mouseReleaseEvent(mouseEvent);
}
void DialogListWidget::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	const BPoint& pos = mouseEvent.pos();
	DialogListItem* hoveredItem = this->itemAt(pos);
	if (_hoveredItem != hoveredItem)
	{
		_hoveredItem = hoveredItem;
		emit(Signal_ItemHovered, hoveredItem);
		this->fresh();
	}
	BScrollArea::mouseMoveEvent(mouseEvent);
}

void DialogListWidget::mouseClickEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		if (_pressedItem)
			emit(Signal_ItemClicked, _pressedItem);
	}
	if (mouseEvent.button() == Button_Right)
	{
		if (_pressedItem)
			emit(Signal_ItemRightClicked, _pressedItem);
	}
	BScrollArea::mouseClickEvent(mouseEvent);
}
void DialogListWidget::mouseDoubleClickEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		if (_pressedItem)
			emit(Signal_ItemDoubleClicked, _pressedItem);
	}
	BScrollArea::mouseDoubleClickEvent(mouseEvent);
}

